#!/usr/bin/perl
#
# The Load data Generation
# (C) 2011, Giuseppe Maxia, Continuent, Inc
# Released under the New BSD License
#
# This program can 
# * generate large amountd of data
# * load them into an existing database
# * start a concurrent batch of SysBench processes
#
use strict;
use warnings;
use Data::Dumper;
use Getopt::Long;
use File::Basename;

our $VERSION = '1.0.0';
our $DRY_RUN = 0;

{

#
# This package contains the main routines
# for the Load Data Generation
#
package LDG;
use English '-no_match_vars';
use File::Basename;
use Data::Dumper;

#
# finds the mysql initialization script
#
sub check_mysql_init
{
    my ($options) = @_;
    unless ( -x $options->{'mysql-init'})
    {
        die "$options->{'mysql-init'} not found or not executable\n";
    }
}

#
# Retrieves the metadata associated with a stored dataset
#
sub get_dataset_manifest
{
    my ($dir) = @_;
    open my $MANIFEST, '<', "$dir/.manifest"
        or die "can't load '.manifest' from $dir ($OS_ERROR)\n";
    my $manifest ='';
    while (my $line = <$MANIFEST>)
    {
        $manifest .= $line;
    }
    close $MANIFEST;
    my $saved_options;
    eval $manifest;
    unless ($saved_options)
    {
        die "can't load '.manifest' from $dir \n";
    }
    return $saved_options;
}


#
# Prints the list of existing datasets
#
sub list_datasets
{
    my ($options) = @_;
    for my $dir (glob("$options->{repository}/*/"))
    {
        my $repo = get_dataset_manifest($dir);
        if ($options->{verbose})
        {
            #
            # When verbose is active, we list all the details associated with each dataset
            #
            for my $op (sort keys %{ $repo } )
            {
                next unless grep { $op eq $_ } qw( data-saved dataset-name dataset-size innodb-data-file-path 
                        innodb-file-per-table mysql-version no-binlog records schemas);
                printf "%30s : %s\n", $op, $repo->{$op} || '';
            }
            print '-' x 80;
            print "\n";
        }
        else
        {
            #
            # Normal listing has only a few chosen fields
            #
            printf "%-20s - date: %s - schemas: %3d - records: %10d - %12s - %s\n",
                $repo->{'dataset-name'},
                $repo->{'data-saved'},
                $repo->{'schemas'},
                $repo->{'records'},
                $repo->{'mysql-version'},
                $repo->{'dataset-size'};
        }
    }
}


#
# Executes a given command with optional arguments.
# If DRY-RUN was requested, it will only show the commands, without executing them
#
sub mysystem
{
    my (@commands) = @_;
    if ($DRY_RUN)
    {
        print ">> @commands\n";
        return 0;
    } 
    return system(@commands);
}


#
# Makes a sandbox using the options passed at the command line
#
sub make_sandbox 
{
    my ($version, $no_binlogs) = @_;
    my $sb_options=' --no_confirm -c innodb-file-per-table=1 ';
    unless ($no_binlogs)
    {
        $sb_options .= '-c log-bin=mysql-bin'
    }
    my $result = mysystem qq(make_sandbox $version $sb_options);
    if ($result)
    {
        die "Error creating sandbox\n";
    }
}

#
# Gets a mysql variable without additional text.
# It is the output of "SHOW VARIABLES LIKE 'XXXX'"
# without header and other fields.
#
sub get_mysql_variable
{
    my ($options, $variable ) = @_;

    my $mysql=qq(mysql -h $options->{'db-host'} -P $options->{'db-port'} -u $options->{'db-user'} -p$options->{'db-password'});
    my $bare_result= qx($mysql -NB -e 'show variables like "$variable"');
    $bare_result =~ s/^\s*\S+\s*//;
    chomp $bare_result;
    return $bare_result; 
}

#
# Writes the propertis to a temporary file.
# This file will then be read by sysbench_wrapper.sh
#
sub write_sysbench_properties
{
    my ($options) = @_;
    open my $HOST_PARAMS, '>', '/tmp/HOST_PARAMS.sh'
        or die "can't create /tmp/HOST_PARAMS.sh ($OS_ERROR)";
    printf $HOST_PARAMS "HOST=%s\n",                $options->{'db-host'};
    printf $HOST_PARAMS "PORT=%s\n",                $options->{'db-port'};
    printf $HOST_PARAMS "DB_USER=%s\n",             $options->{'db-user'};
    printf $HOST_PARAMS "DB_PASSWD=%s\n",           $options->{'db-password'};
    printf $HOST_PARAMS "ROWS=%s\n",                $options->{'records'};
    printf $HOST_PARAMS "NUM_THREADS=%s\n",         $options->{'sysbench-threads'};
    printf $HOST_PARAMS "SYSBENCH_DURATION=%s\n",   $options->{'sysbench-duration'};
    printf $HOST_PARAMS "MAX_REQUESTS=%s\n",        $options->{'sysbench-requests'};
    close $HOST_PARAMS;
 
    my $sysbench_wrapper = dirname($PROGRAM_NAME) . "/sysbench_wrapper.sh";
    unless ( -x $sysbench_wrapper) 
    {
        die "sysbench_wrapper.sh not found\n";
    }
    return $sysbench_wrapper; 
}

#
# The main meat. This routine creates the dataset, using all the options passed at the command line.
#
sub generate_dataset
{
    my ($options) = @_;
    unless ( -d $options->{repository})
    {
        die "Repository $options->{repository} does not exist. Please create the directory or indicate a different one\n";
    }  

    #
    # If a sandbox was requested, it will be created
    #
    if ($options->{'make-sandbox'})
    {
        make_sandbox($options->{'make-sandbox'}, $options->{'no-binlog'});
    }
    elsif ( $options->{'no-binlog'})
    {
        die "option 'no-binlog' is currently supported only with 'make_sandbox'\n";
    }
    my $sysbench_wrapper= write_sysbench_properties($options);

    #
    # Vital info is collected from the database server itself
    #
    $options->{'datadir'}               = get_mysql_variable($options, 'datadir');
    $options->{'mysql-version'}         = get_mysql_variable($options, 'version');
    $options->{'innodb-data-file-path'} = get_mysql_variable($options, 'innodb_data_file_path');
    $options->{'innodb-file-per-table'} = get_mysql_variable($options, 'innodb_file_per_table');
    # print $mysql, "\n", Dumper $options; exit;
    for my $N ( 1 .. $options->{schemas})
    {
        my $number = sprintf('%02d', $N);
        mysystem "$sysbench_wrapper $options->{'schema-prefix'}$number prepare"; 
    }

    if ($options->{datadir}  && ( -d $options->{datadir} ))
    {
        copy_to_repository($options);
    }
    else 
    {
        print "Generation complete. But can't determine data directory\n";
        exit;
    }
}

#
# Copied generated data to the repository
# It will also stop and start the database server as needed.
#
sub copy_to_repository 
{
    my ($options) = @_;
    my $sudo = $options->{'use-sudo'};
    my $destination= "$options->{repository}/$options->{'dataset-name'}";
    check_mysql_init($options);
    mysystem "$sudo $options->{'mysql-init'} stop";
    mkdir "$destination"
        or die "error creating $destination $($OS_ERROR)\n";
    print "Copying to repository. Please wait ... \n";
    my $result = mysystem "cp -R $options->{datadir}/ $destination/";
    if ($result)
    {
        die "An error occurred during the copy from $options->{datadir} to $destination/\n";
    }
    else 
    {
        $options->{'data-saved'} = qx/date '+\%Y-\%m-\%d'/;
        chomp $options->{'data-saved'};
        $options->{'dataset-size'} = qx(du -sh $options->{repository}/$options->{'dataset-name'} | awk '{print \$1}');
        chomp $options->{'dataset-size'};
        open my $MANIFEST, '>', "$destination/.manifest" 
            or die "can't create $destination/.manifest\n";
        print $MANIFEST Data::Dumper->Dump([$options],['saved_options']);
        close $MANIFEST;
    }
    mysystem "$sudo $options->{'mysql-init'} start";
    print "Generated data was copied to $destination\n";
}

#
# Checks dataset compatibility with the requested load
#
# Check that 
# * source dataset exists
# * destination is reachable
# * mysql version is compatible
# * innodb file per table is compatible
# * innodb_data_file_path is compatible
sub check_dataset
{
    my ($options) = @_;
    my $dest_host   = '127.0.0.1';
    my $dest_path   = undef;
    my $source_path = undef;

    if ( -d "$options->{'repository'}/$options->{'dataset-name'}")
    # the origin is a dataset in the default repository
    {
        $source_path = "$options->{'repository'}/$options->{'dataset-name'}";
    }
    unless ($source_path)
    {
        die "$options->{'dataset-name'} not found\n";
    }
    $options->{source_ip} = qx/hostname -i/;
    if ($CHILD_ERROR)
    {
        die "can't determine this host IP address\n";
    }
    chomp $options->{source_ip};
    unless ($options->{source_ip} =~ /^(?:\d+\.){3}\d+$/)
    {
        die "unrecognized IP format. Expected #.#.#.#\n";
    }
    if (($options->{'db-host'} eq $options->{source_ip}) or ($options->{'db-host'} eq '127.0.0.1'))
    {
        $options->{islocal} = 1;
    }
    $options->{datadir} = get_mysql_variable($options, 'datadir');
    my $repo = get_dataset_manifest("$options->{repository}/$options->{'dataset-name'}" );

    my $bare_datadir= $repo->{datadir};
    unless ($bare_datadir) 
    {
        die "can't find 'datadir' in $options->{'dataset-name'} manifest\n";
    }
    $bare_datadir =~ s{/$}{};
    $bare_datadir = basename($bare_datadir);
    $options->{source_path} = "$source_path/$bare_datadir";
    unless (-d $options->{source_path})
    {
        die "could not find datadir $bare_datadir in $options->{'dataset-name'}\n";
    }

    $options->{'mysql-version'}         = get_mysql_variable($options, 'version');
    $options->{'innodb-data-file-path'} = get_mysql_variable($options, 'innodb_data_file_path');
    $options->{'innodb-file-per-table'} = get_mysql_variable($options, 'innodb_file_per_table');

    $options->{'mysql-version'}  = substr($options->{'mysql-version'}, 0, 3);
    $repo->{'mysql-version'}     = substr($repo->{'mysql-version'}, 0, 3);
    for my $op (qw( mysql-version innodb-file-per-table innodb-data-file-path ))
    {
        if ($options->{$op} ne $repo->{$op} )
        {
            die     "Can't load dataset $options->{'dataset-name'}.\n"
                .   "Option '$op' is incompatible.\n"
                .   "Found: $repo->{$op}. Current version: $options->{$op}\n";
        }
    } 
    return $options;
}

#
# Copies data from the repository to the database server
#
sub load_dataset
{
    my ($options) = @_;
    check_mysql_init($options);
    my $copy_command = '';
    #print Dumper $options;
    if ($options->{islocal})
    {
        $copy_command = "$options->{'use-sudo'} $options->{'copy-command'} $options->{'copy-options'} $options->{source_path}/ $options->{datadir}"
    }
    else
    {
        $copy_command = "ssh $options->{'db-host'} $options->{'use-sudo'} $options->{'copy-command'} $options->{'copy-options'} $options->{source_ip}:$options->{source_path}/ $options->{datadir}"
    }
    mysystem("$options->{'mysql-init'} stop");
    mysystem($copy_command);
    mysystem("$options->{'mysql-init'} start");
    #print "Working on this feature (load dataset) Stay tuned ....\n";
}


#
# Removes an existing dataset
#
sub delete_dataset
{
    my ($options) = @_;
    my $dataset = "$options->{repository}/$options->{'dataset-name'}";
    if ( -d $dataset)
    {
        if ( $options->{'i-am-sure'}) 
        {
            mysystem "rm -rf $dataset";
        }
        else 
        {
            die "To delete a dataset you must add the --i-am-sure option\n";
        }
    }
    else 
    {
        die "Dataset $dataset not found\n"
    }
    print "dataset $dataset has been removed\n";
}

#
# Runs sysbench in concurrent processes, as indicated in the options
#
sub run_sysbench
{
    my ($options) = @_;


    my $sysbench_wrapper= write_sysbench_properties($options);

    my $run_sysbench = './tmp_run_sysbench.sh';
    open my $SYSRUN, '>', $run_sysbench
        or die "can't create $run_sysbench ($OS_ERROR)\n";
    for my $N ( 1 .. $options->{schemas})
    {
        my $number = sprintf('%02d', $N);
        print $SYSRUN "$sysbench_wrapper $options->{'schema-prefix'}$number run & \n"; 
    }
    close $SYSRUN;
    chmod 0700, $run_sysbench;
    mysystem("cat $run_sysbench");
    #print "Working on this feature (run sysbench). Stay tuned ....\n";
}

1;
}  # end package LDG


{

#
# General purpose routines.
#
package Utils;
use English '-no_match_vars';

#
# Prints the help, using the data contained in the parsing options
#
sub get_help {
    my ($parse_options, $msg) = @_;
    if ($msg) {
        warn "[***] $msg\n\n";
    }

    my $layout = '[options] operation';
    my $HELP_MSG = q{};
    for my $op (
                sort { $parse_options->{$a}{so} <=> $parse_options->{$b}{so} }
                grep { $parse_options->{$_}{parse}}  keys %{ $parse_options }
               )
    {
        my $param =  $parse_options->{$op}{parse};
        my $param_str = q{    };
        my ($short, $long ) = $param =~ / (?: (\w) \| )? (\S+) /x;
        if ($short)
        {
            $param_str .= q{-} . $short . q{ };
        }
        $long =~ s/ = s \@? / = name/x;
        $long =~ s/ = i / = number/x;
        $param_str .= q{--} . $long;
        $param_str .= (q{ } x (40 - length($param_str)) );
        my $text_items = $parse_options->{$op}{help};
        for my $titem (@{$text_items})
        {
            $HELP_MSG .= $param_str . $titem . "\n";
            $param_str = q{ } x 40;
        }
        if ($parse_options->{$op}{allowed}) 
        {
            $HELP_MSG .=  (q{ } x 40) . "  (Allowed: {@{[join '|', sort keys %{$parse_options->{$op}{allowed}}  ]}})\n"
        }
   }

   print get_credits(),
          "Syntax: $PROGRAM_NAME $layout \n",
          $HELP_MSG;
    exit( defined $msg );
}

#
# Lists development credits
#
sub get_credits 
{
    my $CREDITS =
          qq(    Tungsten Tools,  version $VERSION\n)
        . qq(    ldg - Large Data Generator\n )
        . qq(    (C) 2011 Giuseppe Maxia, Continuent, Inc\n);
    return $CREDITS;
}

#
# Makes sure that the options passed at the command line 
# have the right format, and that the mandatory ones are filled.
#
sub validate_options
{
    my ($options, $parse_options) = @_;
    my @to_be_defined;
    my @not_allowed;
    #
    # Checks that required options are filled
    #
    for my $must ( grep {$parse_options->{$_}->{must_have}} keys %{$parse_options})
    {
        unless (defined $options->{$must})
        {
            my $required = 0;
            if (ref($parse_options->{$must}->{must_have}) && ref($parse_options->{$must}->{must_have}) eq 'ARRAY' )
            # 
            # Conditional requirement, with a list of tasks where it is required
            # Using information in the parsing options, this loop determines if 
            # some options must be filled or not.
            {
                for my $task (@{$parse_options->{$must}->{must_have}})
                {
                    if ($task eq $options->{operation})
                    {
                        $required = 1;
                    }
                }
            }
            elsif ($parse_options->{$must}->{must_have} eq '1')
            # unconditional requirement
            {
                $required=1;
            }
            elsif ($parse_options->{$must}->{must_have} eq $options->{operation})
            # required only for a given operation
            {
                $required = 1;
            }
            push @to_be_defined, $must if $required;
        }
    }

    #
    # Checks that options requiring given keywords are not using anything different
    #
    for my $option (keys %{$options} ) {
        if (exists $parse_options->{$option}{allowed} && $options->{$option})
        {
            unless (exists $parse_options->{$option}{allowed}{$options->{$option}})
            {
                push @not_allowed, "Not allowed value '$options->{$option}' for option '$option' - "
                . " (Choose among: { @{[keys %{$parse_options->{$option}{allowed}} ]} })\n";
            }
        }
    }
    #
    # Reports errors, if any
    #
    if (@to_be_defined)
    {
        for my $must (@to_be_defined)
        {
            print "Option '$must' must be defined\n"
        }
    }
    if (@not_allowed)
    {
        for my $na (@not_allowed) 
        {
            print $na;
        }
    }
    if (@not_allowed or @to_be_defined)
    {
        exit 1;
    }
}

#
# Custom implementation of the 'which' command.
# Returns the full path of the command being searched, or NULL on failure.
#
sub which
{
    my ($executable) = @_;
    for my $dir ( split /:/, $ENV{PATH} )
    {
        $dir =~ s{/$}{};
        if ( -x "$dir/$executable" )
        {
            return "$dir/$executable";
        }
    }
    return;
}

1;
} # end package Utils

package main;

#
# Definition of the options for this application.
#
# Each option has the following fields
# * parse       ->  what will be parsed at the command line
# * value       ->  the default value of this option
# * so          ->  the sort order. Used by the get_help routine to list the options
# * must_have   ->  either a number (inconditional requirement) 
#                   or a keyword list indicating for which operations it is required
# * help        ->  the text to be displayed at the help
# Optionally, a list of 'allowed' keywords can be provided. If so, the application will accept
# only values that match the given list
#
my %parse_options = (

    #
    # Data generation options
    #    

    #
    # This is the most critical option. It defines what the application is going to do.
    #
    operation => {
            parse   => 'o|operation=s',
            value   => 'list',
            so      => 5,
            must_have => 1,
            allowed => {
                'generate'  => 1,   # generates data
                'load'      => 1,   # loads data from the repository
                'list'      => 1,   # lists existing datasets
                'delete'    => 1,   # removes one dataset
                'sysbench'  => 1    # run sysbench in parallel
                },
            help    => ['Operation to perform' ]
        },
    schemas => {
            parse   => 'schemas=i',
            value   => 5,
            so      => 10,
            help    => ['How many schemas to create' ]
        },
    'schema-prefix' => {
            parse   => 'schema-prefix=s',
            value   => 'db',
            must_have => ['generate', 'sysbench'],
            so      => 15,
            help    => ['Name prefix for each database to create' ]
        },
     records => {
            parse   => 'records=i',
            value   => 10_000_000,
            so      => 20,
            help    => ['How many records for each schema' ]
        },
#    'generation-agent' => {
#            parse   => 'generation-agent=s',
#            value   => 'sysbench',
#            so      => 25,
#            allowed => {sysbench => 1},
#            help    => ['Which agent will create the data' ]
#        },
    repository => {
            parse   => 'repository=s',
            value   => "$ENV{HOME}/ldg_repo",
            so      => 30,
            help    => ['Where to store the datasets' ]
        },

    'dataset-name' => {
            parse   => 'dataset-name=s',
            value   => undef,
            must_have => [qw(generate delete load)],
            so      => 40,
            help    => ['Name of this dataset' ]
        },
    'no-binlog' => {
            parse   => 'no-binlog',
            value   => 0,
            so      => 50,
            help    => ['Disables binary log during generation' ]
        },
# 
# Creating the schemas in parallel is going to be extremely slow.
# Serial by default is the way to go
#
#    'generation-flow' => {
#            parse   => 'generation-flow=s',
#            value   => 'parallel',
#            so      => 60,
#            allowed => {serial => 1, parallel =>1},
#            help    => ['Defines whether the schemas are created one by one {serial} or all together {parallel}' ]
#        },
#
# There is no easy way of cloning a table from a database to another in innodb
#
#    'generation-method' => {
#            parse   => 'generation-method=s',
#            value   => 'generate-all',
#            so      => 70,
#            allowed => {'clone-first' => 1, 'generate-all' =>1},
#            help    => ['With "clone-first", one database is created and then copied N times.',
#                        'With "generate-all", every database is created independently' ],
#        },
    'db-host' => {
            parse   => 'db-host=s',
            value   => undef,
            must_have => ['generate','load','sysbench'],
            so      => 80,
            help    => ['name of the host to use for data generation'],
        },
    'db-port' => {
            parse   => 'db-port=i',
            value   => 3306,
            so      => 90,
            help    => ['database server port '],
        },
    'db-user' => {
            parse   => 'db-user=s',
            value   => undef,
            must_have => ['generate','load','sysbench'],
            so      => 100,
            help    => ['database server user '],
        },
    'db-password' => {
            parse   => 'db-password=s',
            value   => undef,
            must_have => ['generate','load','sysbench'],
            so      => 110,
            help    => ['database server password'],
        },
    'make-sandbox' => {
            parse   => 'make-sandbox=s',
            value   => undef,
            so      => 120,
            help    => ['Will use a sandbox to generate data.'],
        },
    'sysbench-threads' => {
            parse   => 'sysbench-threads=i',
            value   => 10,
            so      => 130,
            help    => ['How many threads for sysbench' ]
        },
    'sysbench-duration' => {
            parse   => 'sysbench-duration=i',
            value   => 3600,
            so      => 130,
            help    => ['How many seconds should sysbench run' ]
        },
     'sysbench-requests' => {
            parse   => 'sysbench-requests=i',
            value   => 0,
            so      => 130,
            help    => ['How many requests should sysbench generate' ]
        },
       
    #    
    # Management options
    #

#
#  With the constraint that the source directory is always in the local host,
#  There is no need for 'load-from'. Repository and dataset-name will identify the 
#  source
#
#  Regarding the destination, there is no need for 'load-to'. 
#  The database server will provide the data directory path.
#  All we need is db-host, db-port, db-user, and db-password to get the right info.
#
#    'load-from' => {
#            parse   => 'load-from=s',
#            value   => undef,
#            so      => 400,
#            must_have => 'load',
#            help    => ['Where the data is being copying from.', 
#                        'The parameter must be a simple path in the current host'],
#        },
#    'load-to' => {
#            parse   => 'load-to=s',
#            value   => undef,
#            must_have => 'load',
#            so      => 410,
#            help    => ['Where the data is being copying to.', 
#                        'The parameter can be either a simple path or "hostname:/path/"'],
#        },
    'skip-logs' => {
            parse   => 'skip-logs',
            value   => undef,
            so      => 420,
            help    => ['Do not copy innodb log files']
        },
    'mysql-init' => {
            parse   => 'mysql-init=s',
            value   => '/etc/init.d/mysqld',
            so      => 430,
            help    => ['path to the mysql init script (/etc/init.d/mysqld)',
                        'This command will be called before loading,',
                        'to stop the server, and after loading, to start it.']
        },
    'use-sudo' => {
            parse   => 'use-sudo',
            value   => undef,
            so      => 440,
            help    => ['act as super user to create the data directory']
        },
    'copy-command' => {
            parse   => 'copy-command=s',
            value   => 'rsync',
            so      => 450,
            allowed => {rsync =>1, scp => 1, cp => 1},
            help    => ['which command should be used to copy']
        },
    'copy-options' => {
            parse   => 'copy-options=s',
            value   => '-c -r -v',
            so      => 460,
            help    => ['which options for the copy command']
        },
#    'run-sysbench' => {
#            parse   => 'run-sysbench=s',
#            value   => undef,
#            so      => 450,
#            help    => ['Execute "sysbench run" in the given host after loading the data.']
#        },
    'help' => {
            parse   => 'h|help',
            value   => undef,
            so      => 650,
            help    => ['Shows this help page.']
        },
     'dry-run' => {
            parse   => 'dry-run',
            value   => undef,
            so      => 660,
            help    => ['Shows the requested commands, without actually running them.']
        },
    'i-am-sure' => {
            parse   => 'i-am-sure',
            value   => undef,
            so      => 670,
            help    => ['Option required when asking for dangerous tasks, such as delete datasets.']
        },
     'verbose' => {
            parse   => 'verbose',
            value   => undef,
            so      => 680,
            help    => ['Gives more information on some operations.']
        },
   
);

#
#  Generates the working options set from the parsing options.
#
my %options = map { $_ ,  $parse_options{$_}{'value'}}  keys %parse_options;


#
#  Calls help if no arguments were passed
#
Utils::get_help(\%parse_options,'') unless @ARGV;

#
# Parses the options, using the 'parse' element of each parse_option item
# Calls the help if there is a parsing error.
GetOptions (
    map { $parse_options{$_}{parse}, \$options{$_} }    
        grep { $parse_options{$_}{parse}}  keys %parse_options 
) or Utils::get_help(\%parse_options, '');

if ( @ARGV == 1 )
{
    if (exists $parse_options{operation}{allowed}{ $ARGV[0]} )
    {
        $options{operation}=shift;
    } 
    else 
    {
        die "unknown operation $ARGV[0]\n"
    }
}
elsif (@ARGV > 1)
{
    Utils::get_help(\%parse_options, "only one bare operation is allowed\n");
}

#
# Calls the help if the help option was requested
#
Utils::get_help(\%parse_options,'') if $options{help};

#
# Enables test mode
#
if ($options{'dry-run'})
{
    $DRY_RUN = 1;
}

#
# If a sandbox is requested, All the database defaults are overwritten
# using the sandbox being created.
#
if ($options{'make-sandbox'})
{
    my $mysql_version= $options{'make-sandbox'};
    unless ($mysql_version =~ /^\d\.\d\.\d\d$/)
    {
        die "make-sandbox value must be a valid MySQL version\n";
    }
    my $make_sandbox= Utils::which('make_sandbox');
    unless ($make_sandbox)
    {
        die "Can't find 'make_sandbox' in $ENV{PATH}\n";
    }
    my $binary_base = "$ENV{HOME}/opt/mysql";
    if ($ENV{SANDBOX_BINARY})
    {
        $binary_base = $ENV{SANDBOX_BINARY};
    }
    unless ( -d "$binary_base/$mysql_version" )
    {
       die "To use MySQL sandbox you must expand the MySQL tarball under $binary_base, and "
           . " pass only the version number to this application.\n"
           . "Please refer to the MySQL Sandbox docs for more\n" 
    }
    $options{'db-host'} = '127.0.0.1';
    $options{'db-user'} = 'msandbox';
    $options{'db-password'} = 'msandbox';
    my $mysql_port = $mysql_version;
    my $mysql_dir = $mysql_version;
    $mysql_port =~ s/\.//g;
    $mysql_dir =~ s/\./_/g;
    $options{'mysql-init'} = "$ENV{HOME}/sandboxes/msb_$mysql_dir/msb";
    $options{'db-port'} = $mysql_port;
} # make_sandbox

#
# If a dataset name is not provided, a default one is created, using the information at hand
#
unless ($options{'dataset-name'})
{
    if ($options{operation} eq 'generate')
    {
        $options{'dataset-name'} = sprintf('db_%d_rec_%d',$options{schemas},$options{records} );
    }
}

#
#  Makes sure that the options conform to the expectations
#
Utils::validate_options(
    \%options, 
    \%parse_options);

#
# Gets the 'sudo' command, if needed
#
if ($options{'use-sudo'})
{
    $options{'use-sudo'} = Util::which('sudo');
    unless ($options{'use-sudo'})
    {
        die "can't find 'sudo' in $ENV{PATH}\n";
    }
}
else
{
    $options{'use-sudo'} = '';
}

#
# check for sysbench
#
my $sysbench = Utils::which('sysbench');
unless ($sysbench)
{
    die "could not find 'sysbench' in $ENV{PATH}\n";
}

#
# Make sure that sysbench requests are runnable
#
if ( $options{'sysbench-requests'} && $options{'sysbench-duration'})
{
    die  "sysbench-requests and sysbench-duration are mutually exclusive\n"
       . "If you set the duration, requests should be 0, and vice versa\n";
}

#
# Refuses to overwrite an existing dataset
#

if ((  $options{operation} eq 'generate') && (-d "$options{repository}/$options{'dataset-name'}") )
{
    die "dataset already exists in $options{repository}/$options{'dataset-name'}\n";
}

# print Dumper \%options; exit;

#
###################################
# Executes the requested operation
###################################
#

if ($options{operation}     eq      'list')
{
    LDG::list_datasets(\%options);
}
elsif ($options{operation}  eq      'generate')
{
    LDG::generate_dataset(\%options);
    # check that MySQL database is reachable
    # check that data directory exists
}
elsif ($options{operation}  eq      'delete')
{
    LDG::delete_dataset(\%options);
}
elsif ($options{operation}  eq      'load')
{
    my $update_options = LDG::check_dataset(\%options); 
    LDG::load_dataset($update_options);
}
elsif ($options{operation} eq       'sysbench')
{
    LDG::run_sysbench(\%options);
}
else
{
    die "Unhandled operation $options{operation}\n"
}
#
# The end
#

